﻿using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using UIOC.CodeAnalysis.Extensions;

namespace UIOC.CodeAnalysis.Helpers;

internal static class SourceOutputHelper {

    // Please do not make these public, everything should depend calling Setup on this static type correctly.
    // These are typically setup the first time the generator runs (or every run), they rely on common state
    // across a compilation so there should be no issue if they are setup multiple times from multiple generators.
    static string s_ProjectPath = string.Empty;
    public static string ProjectPath => s_ProjectPath;
    public static bool CanWriteToProjectPath => !string.IsNullOrEmpty(s_ProjectPath);
    static bool s_OutputSourceGenFiles;
    public static bool OutputSourceGenFiles => s_OutputSourceGenFiles;

    /// <summary>
    /// 向上查找项目目录路径
    /// </summary>
    /// <param name="startPath">起始文件路径</param>
    /// <returns>项目目录路径，未找到则返回 null</returns>
    private static string FindProjectDirectory(string startPath) {
        // 获取起始路径的目录（如果是文件路径，则取其所在目录）
        string currentDir = Path.GetDirectoryName(startPath) ?? startPath;

        // 循环向上查找，直到根目录
        while (!string.IsNullOrEmpty(currentDir)) {
            // 检查当前目录是否包含 "Assets" 子目录
            string assetsPath = Path.Combine(currentDir, "Assets");
            if (Directory.Exists(assetsPath)) {
                return currentDir; // 返回 Assets 目录的完整路径
            }

            // 向上移动一级目录
            string parentDir = Path.GetDirectoryName(currentDir);
            if (parentDir == currentDir) // 到达根目录（如 C:\ 或 /）
            {
                break;
            }
            currentDir = parentDir;
        }

        return null; // 未找到
    }

    public static void Setup(SyntaxReference reference) {
        if (Environment.GetEnvironmentVariable("SOURCEGEN_DISABLE_PROJECT_PATH_OUTPUT") == "1")
            return;

        if (reference == null) {
            return;
        }

        var syntaxTree = reference.SyntaxTree;
        string filePath = syntaxTree.FilePath;
        if (string.IsNullOrWhiteSpace(filePath)) {
            return;
        }

        s_ProjectPath = FindProjectDirectory(filePath);
        s_OutputSourceGenFiles = true;
    }

    public static void Setup(SyntaxTree syntaxTree) {
        if (Environment.GetEnvironmentVariable("SOURCEGEN_DISABLE_PROJECT_PATH_OUTPUT") == "1")
            return;

        string filePath = syntaxTree.FilePath;
        if (string.IsNullOrWhiteSpace(filePath)) {
            return;
        }

        s_ProjectPath = FindProjectDirectory(filePath);
        s_OutputSourceGenFiles = true;
    }

    public static void Setup(ImmutableArray<AdditionalText> additionalFiles = default) {
        if (s_OutputSourceGenFiles) {
            return;
        }

        if (Environment.GetEnvironmentVariable("SOURCEGEN_DISABLE_PROJECT_PATH_OUTPUT") == "1") {
            return;
        }

        if (additionalFiles == null || !additionalFiles.Any() || string.IsNullOrEmpty(additionalFiles[0].Path)) {
            return;
        }

        s_ProjectPath = additionalFiles[0].GetText().ToString();
        s_OutputSourceGenFiles = true;
    }

    public static string GetGeneratedCodePath() {
        string generatedCodePath;
        if (!CanWriteToProjectPath)
            generatedCodePath = Path.Combine("Temp", "GeneratedCode");
        else {
            generatedCodePath = Path.Combine(s_ProjectPath, "Temp", "GeneratedCode");
            Directory.CreateDirectory(generatedCodePath);
        }
        return generatedCodePath;
    }

    // Output as generated source file for debugging/inspection (used by incremental generators)
    public static void OutputSourceToFile(string generatedSourceFilePath, Func<string> sourceTextProvider) {
        if (!CanWriteToProjectPath || !s_OutputSourceGenFiles)
            return;

        try {
            LogInfo(DiagnosticSeverity.Info, $"Outputting generated source to file {generatedSourceFilePath}...");
            if (Path.GetDirectoryName(generatedSourceFilePath) is { } dirPath) {
                Directory.CreateDirectory(dirPath);
            }
            File.WriteAllText(generatedSourceFilePath, sourceTextProvider());
        } catch (IOException ioException) {
            // emit Entities exceptions as info but don't block compilation or generate error to fail tests
            LogInfo(DiagnosticSeverity.Error,
                @$"Error trying to write generated source for file {generatedSourceFilePath}
                    {ioException.ToUnityPrintableString()}...");
        }
    }

    public static void LogInfo(DiagnosticSeverity type, string message) {
        if (!s_OutputSourceGenFiles || !CanWriteToProjectPath) {
            return;
        }
        try {
            string generatedCodePath = GetGeneratedCodePath();
            var sourceGenLogPath = Path.Combine(generatedCodePath, "SourceGen.log");
            using var writer = File.AppendText(sourceGenLogPath);
            writer.WriteLine($"[{DateTime.Now}][Thread {Thread.CurrentThread.ManagedThreadId}][{type}]{message}");
        } catch (IOException) { }
    }
}